/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021.
 * Description: rtos feature dump interface
 * Author: zhuyan
 * Create: 2021/6/15
 */
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/rtos_dump.h>
#include <linux/vmalloc.h>
#include <internal/rtos_dump_skip_ioaddr.h>
#include <linux/mm.h>

/*
 * NOTE: The 'address' parameter must be guaranteed to be valid
 * before calling do_show_data.
 */
#define UINT_BYTE_NUM            (sizeof(unsigned int))
#define DATA_SIZE               9
#define LINE_DATA_NUM           8
#define LINE_BYTES              (UINT_BYTE_NUM * LINE_DATA_NUM)
#define BYTE_ALIGN_MASK         (UINT_BYTE_NUM - 1)
#define LINE_ALIGN_MASK         (LINE_BYTES - 1)
#define ADDRESS_ALIGN_MASK      ((-1UL) << BYTE_ALIGN_MASK)

/**
 * is_risky_addr - determine whether an addr is risky addr
 * @addr:	  virtual address
 *
 * Search for the kernel VM area starting at @addr, and return it.
 * Then determine whether this VMA is valid and whether it's type is ioaddr.
 *
 * Return false if it's not vmalloc addr,and return false if it's vmalloc addr
 * but not VM_IOREMAP, else return true.
 */
static bool is_risky_addr(unsigned long addr)
{
	struct vm_struct *area;
	if (!is_ioremap_addr((void *)addr))
		return false;
	area = try_find_vm_area(addr);
	if (area && !(area->flags & VM_IOREMAP))
		return false;
	else
		return true;
}

static void general_show_data(unsigned long address, int bytes,
			      const char *reg_name, bool is_user)
{
	unsigned long start_addr;
	unsigned long end_addr;
	unsigned long cur_addr;
	unsigned int temp_data;
	int err;
	int i;

	if (bytes <= 0 || reg_name == NULL)
		return;
	start_addr = address & ADDRESS_ALIGN_MASK;
	end_addr = start_addr + DIV_ROUND_UP(bytes, LINE_BYTES) * LINE_BYTES;
	pr_info("\n%s: %#lx:\n", reg_name, address);
	for (cur_addr = start_addr;  cur_addr < end_addr; cur_addr += LINE_BYTES) {
		unsigned long p;
		char str[DATA_SIZE * LINE_DATA_NUM + 1] = {0};
		if (is_risky_addr(cur_addr)) {
			pr_info("skip ioaddr: %#lx\n", cur_addr);
			continue;
		}
		for (p = cur_addr, i = 0; i < LINE_DATA_NUM; i++, p += UINT_BYTE_NUM) {
			if (!is_user)
				err = get_kernel_nofault(temp_data, (const void *)p);
			else
				err = get_user_nofault(temp_data, (const void *)p);
			if (err && !is_user)
				sprintf(str + i * DATA_SIZE, " ********");
			else
				sprintf(str + i * DATA_SIZE, " %08x", temp_data);
		}
		pr_info("%04lx:%s\n", cur_addr & 0xffff, str);
	}
}

void do_show_data(unsigned long address, int bytes, const char *reg_name)
{
	general_show_data(address, bytes, reg_name, false);
}

void do_show_user_data(unsigned long address, int bytes, const char *reg_name)
{
	general_show_data(address, bytes, reg_name, true);
}

#if defined(CONFIG_RTOS_DEBUG_LOCK_MEM_DUMP) || defined(CONFIG_RTOS_LOCK_STATE_CHANGE_CHECK)
#define DUMP_PTR_BEFORE         32
#define DUMP_PTR_SIZE           64
void rtos_dump_lock_mem(unsigned long addr)
{
	do_show_data(addr - DUMP_PTR_BEFORE, DUMP_PTR_SIZE, "dump lock");
}
#endif

